﻿using System;
using System.Collections.Generic;
using System.IO;
using FlagLib.Extensions;
using FlagLib.Reflection;

namespace FlagTunes.Core
{
    public class FlagTunesController : IDisposable
    {
        private readonly AudioPlayer audioPlayer;
        private readonly Library library;
        private Playlist currentPlaylist;
        private ICollection<Song> filteredSongs;
        private int currentIndex;

        /// <summary>
        /// Gets the playlists.
        /// </summary>
        /// <value>The playlists.</value>
        public IEnumerable<Playlist> Playlists
        {
            get { return this.library.Playlists; }
        }

        /// <summary>
        /// Gets or sets the current playlist.
        /// </summary>
        /// <value>The current playlist.</value>
        public Playlist CurrentPlaylist
        {
            get { return this.currentPlaylist; }
            set
            {
                this.currentPlaylist = value;
                this.filteredSongs = this.currentPlaylist.Songs;
            }
        }

        /// <summary>
        /// Gets all songs.
        /// </summary>
        public IEnumerable<Song> AllSongs
        {
            get { return this.library.MainPlaylist.Songs; }
        }

        /// <summary>
        /// Gets the filtered songs.
        /// </summary>
        /// <value>The filtered songs.</value>
        public ICollection<Song> FilteredSongs
        {
            get { return this.filteredSongs; }
        }

        /// <summary>
        /// Gets the loaded song.
        /// </summary>
        /// <value>The loaded song.</value>
        public Song LoadedSong
        {
            get { return this.audioPlayer.LoadedSong; }
        }

        /// <summary>
        /// Gets or sets the player volume.
        /// </summary>
        /// <value>The player volume.</value>
        public float PlayerVolume
        {
            get { return this.audioPlayer.Volume; }
            set { this.audioPlayer.Volume = value; }
        }

        /// <summary>
        /// Gets the total time of the loaded song.
        /// </summary>
        /// <value>The total time of the loaded song.</value>
        public TimeSpan LoadedSongTotalTime
        {
            get { return this.audioPlayer.TotalTime; }
        }

        /// <summary>
        /// Gets the played time of the loaded song.
        /// </summary>
        /// <value>The played time of the loaded song.</value>
        public TimeSpan LoadedSongElapsedTime
        {
            get { return this.audioPlayer.CurrentTime; }
            private set { this.audioPlayer.CurrentTime = value; }
        }

        /// <summary>
        /// Occurs when a song gets added to the library.
        /// </summary>
        public event EventHandler<SongEventArgs> LibrarySongAdded;

        /// <summary>
        /// Occurs when the library filling has finished.
        /// </summary>
        public event EventHandler LibraryFillFinished;

        /// <summary>
        /// Occurs when the current song has changed.
        /// </summary>
        public event EventHandler<SongEventArgs> SongChanged;

        /// <summary>
        /// Initializes a new instance of the <see cref="FlagTunesController"/> class.
        /// </summary>
        public FlagTunesController()
        {
            this.library = new Library();
            this.library.SongAdded += library_SongAdded;
            this.library.CreationFinished += library_CreationFinished;

            this.audioPlayer = new AudioPlayer();
            this.audioPlayer.SongFinished += audioPlayer_SongFinished;

            this.CurrentPlaylist = this.library.MainPlaylist;
        }

        /// <summary>
        /// Filters the loaded library.
        /// </summary>
        /// <param name="searchPattern">The search pattern.</param>
        public void FilterLoadedPlaylist(string searchPattern)
        {
            this.filteredSongs = PlaylistFilter.FilterPlaylist(this.currentPlaylist, searchPattern);
        }

        /// <summary>
        /// Filters the loaded playlist by the specified artist.
        /// </summary>
        /// <param name="artist">The artist.</param>
        public void FilterLoadedPlaylistByArtist(string artist)
        {
            this.filteredSongs = PlaylistFilter.FilterPlaylistByArtist(this.currentPlaylist, artist);
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        public void Dispose()
        {
            this.audioPlayer.Dispose();
        }

        /// <summary>
        /// Fills the library with music files from the specified path.
        /// </summary>
        /// <param name="path">The path of the directory which contains the music files.</param>
        public void AddSongs(string path)
        {
            path.ThrowIfNull(() => path);

            if (!Directory.Exists(path))
                throw new ArgumentException("The directory doesn't exist.", Reflector.GetMemberName(() => path));

            this.library.AddSongsFromDirectory(new DirectoryInfo(path));
        }

        /// <summary>
        /// Loads the song from play list.
        /// </summary>
        /// <param name="song">The song.</param>
        public void LoadSongFromPlayList(Song song)
        {
            if (this.currentPlaylist.Songs.Contains(song))
            {
                this.currentIndex = this.currentPlaylist.Songs.IndexOf(song);
                this.audioPlayer.Load(song);
            }
        }

        /// <summary>
        /// Pauses the playing song.
        /// </summary>
        public void PauseSong()
        {
            this.audioPlayer.Pause();
        }

        /// <summary>
        /// Plays the loaded song.
        /// </summary>
        public void PlaySong()
        {
            this.audioPlayer.Play();
        }

        /// <summary>
        /// Stops the current playing song.
        /// </summary>
        public void StopSong()
        {
            this.audioPlayer.Stop();
        }

        /// <summary>
        /// Plays the or continues song, dependig on the playback state.
        /// </summary>
        public void PlayOrContinueSong()
        {
            switch (this.audioPlayer.PlaybackState)
            {
                case NAudio.Wave.PlaybackState.Paused:
                    this.PlaySong();
                    break;

                case NAudio.Wave.PlaybackState.Playing:
                    this.PauseSong();
                    break;
            }
        }

        /// <summary>
        /// Changes the loaded song's played time.
        /// </summary>
        /// <param name="seconds">The seconds to change, value can be positive to fast forward or negative to rewind.</param>
        public void ChangeSongElapsedTime(int seconds)
        {
            this.LoadedSongElapsedTime = this.LoadedSongElapsedTime.Add(new TimeSpan(0, 0, seconds));
        }

        /// <summary>
        /// Plays the next song of the loaded playlist.
        /// </summary>
        public void PlayNextSong()
        {
            int nextIndex;

            if (this.currentIndex + 1 < this.currentPlaylist.Songs.Count)
            {
                nextIndex = this.currentIndex + 1;
                this.LoadNextSong(nextIndex);
            }

            else if (this.currentPlaylist.Repeat)
            {
                nextIndex = 0;
                this.LoadNextSong(nextIndex);
            }

            else
            {
                this.StopSong();
                this.OnSongChanged(new SongEventArgs(null));
            }
        }

        /// <summary>
        /// Raises the <see cref="SongChanged"/> event.
        /// </summary>
        /// <param name="e">The <see cref="FlagTunes.Core.SongEventArgs"/> instance containing the event data.</param>
        protected virtual void OnSongChanged(SongEventArgs e)
        {
            if (this.SongChanged != null)
            {
                this.SongChanged(this, e);
            }
        }

        /// <summary>
        /// Handles the SongAdded event of the library control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="FlagTunes.Core.SongEventArgs"/> instance containing the event data.</param>
        private void library_SongAdded(object sender, SongEventArgs e)
        {
            if (this.LibrarySongAdded != null)
            {
                this.LibrarySongAdded(this, new SongEventArgs(e.Song));
            }
        }

        /// <summary>
        /// Handles the CreationFinished event of the library control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void library_CreationFinished(object sender, EventArgs e)
        {
            if (this.LibraryFillFinished != null)
            {
                this.LibraryFillFinished(this, e);
            }
        }

        /// <summary>
        /// Handles the SongFinished event of the audioPlayer control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void audioPlayer_SongFinished(object sender, EventArgs e)
        {
            this.PlayNextSong();
        }

        /// <summary>
        /// Loads the next song from the loaded playlist.
        /// </summary>
        /// <param name="index">The index of the song in the loaded playlist.</param>
        private void LoadNextSong(int index)
        {
            Song nextSong = this.currentPlaylist.Songs[index];

            this.LoadSongFromPlayList(nextSong);
            this.PlaySong();
            this.OnSongChanged(new SongEventArgs(nextSong));
        }
    }
}